package updatex

import (
	"bufio"
	"fmt"
	"io/fs"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
)

type Config struct {
	FileCount int    `json:"file_count"`
	GetCount  int    `json:"get_count"`
	RootDir   string `json:"root_dir"`
	ModTidy   bool   `json:"mod_tidy"`
	UAll      bool   `json:"u_all"`
}

var DefaultConfig = Config{
	FileCount: 1,
	GetCount:  1,
	RootDir:   "./",
	ModTidy:   true,
	UAll:      false,
}
var VersionFlag = regexp.MustCompile(` v.+`)

func IsNext() bool {
	fmt.Println("Continue Y/N: ")
	var target string
	_, err := fmt.Scan(&target)
	if err != nil {
		return false
	}
	return strings.TrimSpace(strings.ToLower(target)) == "y"
}

type GoUpdate struct {
	Config Config
}

func NewGoUpdate(defaultConfig Config) GoUpdate {
	return GoUpdate{Config: defaultConfig}
}

func (g GoUpdate) WalkFindGoMod() []string {
	files := make([]string, 0, 4)
	filepath.WalkDir(g.Config.RootDir, func(path string, d fs.DirEntry, err error) error {
		if d.IsDir() {
			return nil
		}
		if filepath.Base(path) != "go.mod" {
			return nil
		}
		files = append(files, path)
		return nil
	})
	return files
}

func (g GoUpdate) Run(mods []string, handle func(modFilePath string)) {
	var wg sync.WaitGroup
	bools := make(chan bool, g.Config.FileCount)
	for _, mod := range mods {
		wg.Go(func() {
			<-bools
			handle(mod)
		})
		bools <- false
	}
	wg.Wait()
}

func (g GoUpdate) handleModFile(modFilePath string) {
	if g.Config.ModTidy {
		defer func() {
			command := exec.Command("go", "mod", "tidy")
			command.Dir = filepath.Dir(modFilePath)
			output, err := command.CombinedOutput()
			if err != nil {
				fmt.Println("mod tidy err", err)
				return
			}
			fmt.Println("mod tidy ==>", command.String(), " |==> ", string(output))
		}()
	}
	if g.Config.UAll {
		g.getUAll(modFilePath)
		return
	}

	file, err := os.Open(modFilePath)
	if err != nil {
		return
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)
	//=========
	depCount := make(chan bool, g.Config.FileCount)
	var wg sync.WaitGroup
	for scanner.Scan() {
		text := scanner.Text()
		if !VersionFlag.MatchString(text) {
			continue
		}
		dep := strings.TrimSpace(VersionFlag.ReplaceAllString(text, ""))
		wg.Go(func() {
			depCount <- false
			g.exeGoGet(modFilePath, dep)
			<-depCount

		})
	}
	wg.Wait()
}

func (g GoUpdate) ParseFileExec(modFilePath string) {
	g.handleModFile(modFilePath)
}

func (g GoUpdate) getUAll(modFilePath string) {
	command := exec.Command("go", "get", "-u", "all")
	command.Dir = filepath.Dir(modFilePath)
	output, err := command.CombinedOutput()
	if err != nil {
		fmt.Println("go get -u all", modFilePath, err)
		return
	}
	fmt.Println("go get -u all ==>", modFilePath, command.String(), " |==> ", string(output))
}

func (g GoUpdate) exeGoGet(modFilePath string, depString string) {
	command := exec.Command("go", "get", "-v", "-u", depString)
	command.Dir = filepath.Dir(modFilePath)
	output, err := command.CombinedOutput()
	if err != nil {
		log.Println("command err", err)
		return
	}
	fmt.Println("go get ==>", command.String(), " ==> ", string(output))
	return
}
